package com.way.capture.core.screenrecord;

import android.animation.TimeInterpolator;
import android.animation.ValueAnimator;
import android.content.Context;
import android.content.pm.ActivityInfo;
import android.graphics.Point;
import android.graphics.Rect;
import android.os.SystemClock;
import android.text.format.DateUtils;
import android.util.Log;
import android.util.Size;
import android.view.Gravity;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewTreeObserver;
import android.view.WindowManager;
import android.view.animation.OvershootInterpolator;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.TextView;

import com.way.capture.R;
import com.way.capture.utils.RxCountDown;
import com.way.capture.utils.RxView;
import com.way.capture.utils.Settings;
import com.way.capture.utils.Utils;

import java.util.concurrent.TimeUnit;

import io.reactivex.disposables.CompositeDisposable;

import static android.graphics.PixelFormat.TRANSLUCENT;
import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR;
import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN;
import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS;
import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
import static android.view.WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH;

final class OverlayView extends FrameLayout implements ViewTreeObserver.OnGlobalLayoutListener {
    private static final String TAG = "OverlayView";
    private static final int COUNTDOWN_MAX = 3;
    private static final long MOVE_TO_EDGE_DURATION = 450L;
    private static final float MOVE_TO_EDGE_OVERSHOOT_TENSION = 1.25f;
    private final Listener mListener;
    private final TimeInterpolator mMoveEdgeInterpolator;
    private TextView mRecordingTimeTextView;
    private ImageView mStartRecordButton;
    private View mCloseButton;
    private long mLastFiredTime;
    private WindowManager.LayoutParams mParams;

    //状态栏高度
    private int mStatusBarHeight;
    //按下时OverlayView内的坐标
    private Point mDownViewPoint = new Point();
    //按下时屏幕内的坐标
    private Point mDownScreenPoint = new Point();
    //是否拦截Touch事件
    private boolean mIsIntercept;
    //屏幕尺寸
    private Size mScreenSize;
    //偏移量
    private int mOffset;
    //触发拖动的阀值
    private int mThreshold;
    //OverlayView可拖动的区域
    private Rect mBoundRect = new Rect();
    //按下后OverlayView可拖动的区域，基于按下位置做的偏移
    private Rect mDownBoundRect = new Rect();
    private CompositeDisposable mDisposable = new CompositeDisposable();

    private OverlayView(Context context, Listener listener) {
        super(context);
        this.mListener = listener;
        mStatusBarHeight = Utils.getStatusBarHeight();
        mScreenSize = new Size(Utils.getWidth(), Utils.getHeight());
        mOffset = Utils.dp2px(4);
        mThreshold = Utils.dp2px(8);
        mMoveEdgeInterpolator = new OvershootInterpolator(MOVE_TO_EDGE_OVERSHOOT_TENSION);
        initViews(context);
    }

    static OverlayView create(Context context, Listener listener) {
        return new OverlayView(context, listener);
    }

    WindowManager.LayoutParams createLayoutParams() {
        int width = WindowManager.LayoutParams.WRAP_CONTENT;
        int height = WindowManager.LayoutParams.WRAP_CONTENT;
        mParams = new WindowManager.LayoutParams(width, height,
                Utils.getFloatType(), FLAG_NOT_FOCUSABLE | FLAG_NOT_TOUCH_MODAL
                | FLAG_WATCH_OUTSIDE_TOUCH | FLAG_LAYOUT_NO_LIMITS
                | FLAG_LAYOUT_INSET_DECOR | FLAG_LAYOUT_IN_SCREEN, TRANSLUCENT);
        mParams.screenOrientation = ActivityInfo.SCREEN_ORIENTATION_LOCKED;
        mParams.y = mScreenSize.getHeight() / 2;
        mParams.x = 0;
        mParams.gravity = Gravity.START | Gravity.TOP;
        return mParams;
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent event) {
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                mIsIntercept = false;

                mDownViewPoint.x = (int) event.getX();
                mDownViewPoint.y = (int) event.getY();

                mDownBoundRect.left = mBoundRect.left + mDownViewPoint.x;
                mDownBoundRect.top = mBoundRect.top + mDownViewPoint.y;
                mDownBoundRect.right = mBoundRect.right + mDownViewPoint.x;
                mDownBoundRect.bottom = mBoundRect.bottom + mDownViewPoint.y;

                mDownScreenPoint.x = (int) event.getRawX();
                mDownScreenPoint.y = (int) event.getRawY();
                Log.d(TAG, "onInterceptTouchEvent: ACTION_DOWN mDownScreenPoint = " + mDownScreenPoint
                        + ", mDownBoundRect = " + mDownBoundRect
                        + ", mDownViewPoint = " + mDownViewPoint);
                break;
            case MotionEvent.ACTION_MOVE:
                int moveX = (int) event.getRawX();
                int moveY = (int) event.getRawY();
                if (!mIsIntercept && (Math.abs(mDownScreenPoint.x - moveX) > mThreshold || Math.abs(mDownScreenPoint.y - moveY) > mThreshold)) {
                    mIsIntercept = true;
                    Log.d(TAG, "onInterceptTouchEvent: ACTION_MOVE " + moveX + "x" + moveY);
                }
                break;
            case MotionEvent.ACTION_UP:
            case MotionEvent.ACTION_CANCEL:
                int upX = (int) event.getRawX();
                int upY = (int) event.getRawY();
                Log.d(TAG, "onInterceptTouchEvent: ACTION_UP " + upX + "x" + upY);
                mIsIntercept = false;
                break;
        }
        return mIsIntercept;
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                int x = (int) event.getRawX();
                int y = (int) event.getRawY();
                Log.d(TAG, "onTouchEvent: ACTION_DOWN " + x + "x" + y);
                break;
            case MotionEvent.ACTION_MOVE:
                int moveX = (int) event.getRawX();
                int moveY = (int) event.getRawY();
                if (Math.abs(mDownScreenPoint.x - moveX) < mThreshold && Math.abs(mDownScreenPoint.y - moveY) < mThreshold) {
                    break;
                }
                if (moveX < mDownBoundRect.left) moveX = mDownBoundRect.left;
                if (moveX > mDownBoundRect.right) moveX = mDownBoundRect.right;
                if (moveY < mDownBoundRect.top) moveY = mDownBoundRect.top;
                if (moveY > mDownBoundRect.bottom) moveY = mDownBoundRect.bottom;
                Log.d(TAG, "onTouchEvent: ACTION_MOVE " + moveX + "x" + moveY);
                mParams.x = moveX - mDownViewPoint.x;
                mParams.y = moveY - mDownViewPoint.y;
                mListener.updateViewLayout(mParams);
                break;
            case MotionEvent.ACTION_UP:
            case MotionEvent.ACTION_CANCEL:
                int upX = (int) event.getRawX();
                int upY = (int) event.getRawY();
                Log.d(TAG, "onTouchEvent: ACTION_UP " + upX + "x" + upY);
                if (upX < mDownBoundRect.left) upX = mDownBoundRect.left;
                if (upX > mDownBoundRect.right) upX = mDownBoundRect.right;
                if (upY < mDownBoundRect.top) upY = mDownBoundRect.top;
                if (upY > mDownBoundRect.bottom) upY = mDownBoundRect.bottom;
                moveToEdge(upX - mDownViewPoint.x, upY - mDownViewPoint.y);
                break;
        }
        return super.onTouchEvent(event);
    }

    private void moveToEdge(int x, int y) {
        int edgeX = x < mBoundRect.centerX() ? mBoundRect.left : mBoundRect.right;
        mParams.y = y;
        ValueAnimator moveEdgeAnimator = ValueAnimator.ofInt(x, edgeX);
        moveEdgeAnimator.addUpdateListener(animation -> {
            mParams.x = (Integer) animation.getAnimatedValue();
            mListener.updateViewLayout(mParams);
        });
        moveEdgeAnimator.setDuration(MOVE_TO_EDGE_DURATION);
        moveEdgeAnimator.setInterpolator(mMoveEdgeInterpolator);
        moveEdgeAnimator.start();
    }

    @Override
    protected void onDetachedFromWindow() {
        super.onDetachedFromWindow();
        mDisposable.clear();
    }

    private void initViews(Context context) {
        inflate(context, R.layout.layout_float_view, this);

        mStartRecordButton = findViewById(R.id.screen_record_start);
        mStartRecordButton.setImageResource(R.drawable.start);
        mStartRecordButton.setSelected(false);
        mCloseButton = findViewById(R.id.screen_record_close);
        mRecordingTimeTextView = findViewById(R.id.start_text);
        mDisposable.add(RxView.clicks(mStartRecordButton)
                .throttleFirst(2, TimeUnit.SECONDS)
                .subscribe(o -> {
                    if (mStartRecordButton.isSelected()) {
                        mListener.onStopClick();
                    } else {
                        checkCountDown();
                    }
                    mStartRecordButton.setSelected(!mStartRecordButton.isSelected());
                }));
        mDisposable.add(RxView.clicks(mCloseButton)
                .throttleFirst(2, TimeUnit.SECONDS)
                .subscribe(o -> mListener.onCancelClick()));
        getViewTreeObserver().addOnGlobalLayoutListener(this);
    }

    void checkCountDown() {
        mCloseButton.setVisibility(View.GONE);
        getViewTreeObserver().addOnGlobalLayoutListener(this);
        boolean isCountDown = Settings.getInstance().isVideoCountdown();
        if (!isCountDown) {
            startRecording();
        } else {
            mStartRecordButton.setEnabled(false);
            mDisposable.add(RxCountDown.getCountDown(COUNTDOWN_MAX)
                    .subscribe(time -> {
                        if (time == 0L) {
                            startRecording();
                        } else {
                            mRecordingTimeTextView.setText(String.format("  %d  ", time));
                        }
                    }));
        }
    }

    private void startRecording() {
        mRecordingTimeTextView.setText("00:00");
        mListener.onStartClick();
        mStartRecordButton.setImageResource(R.drawable.stop);
        mStartRecordButton.setEnabled(true);
    }

    public void updateRecordingTime(long time) {
        if (SystemClock.elapsedRealtime() - mLastFiredTime < 1000) {
            return;
        }
        mRecordingTimeTextView.setText(DateUtils.formatElapsedTime(time / 1000));
        mLastFiredTime = SystemClock.elapsedRealtime();
    }

    @Override
    public void onGlobalLayout() {
        getViewTreeObserver().removeOnGlobalLayoutListener(this);
        Size viewSize = new Size(getWidth(), getHeight());
        final boolean isLandscape = Utils.isLandscape(getContext());
        mBoundRect.left = mOffset;
        mBoundRect.top = mStatusBarHeight + mOffset;
        mBoundRect.right = mScreenSize.getWidth() - mOffset - viewSize.getWidth();
        if (isLandscape && Utils.isNavigationBarShow()) {
            mBoundRect.right -= Utils.getNavigationBarHeight();
        }
        mBoundRect.bottom = mScreenSize.getHeight() - mOffset - viewSize.getHeight();
        if (!isLandscape && Utils.isNavigationBarShow()) {
            mBoundRect.bottom -= Utils.getNavigationBarHeight();
        }

        Log.d(TAG, "onGlobalLayout: mViewSize = " + viewSize
                + ", mBoundRect = " + mBoundRect
                + ", isNavigationBarShow = " + Utils.isNavigationBarShow()
                + ", isLandscape = " + isLandscape
        );
        if (mCloseButton.getVisibility() == VISIBLE) {
            int x = mScreenSize.getWidth() - viewSize.getWidth() - mOffset;
            if (isLandscape) {
                x -= Utils.getNavigationBarHeight();
            }
            mParams.x = x;
            mParams.y = mScreenSize.getHeight() / 2;
            mListener.updateViewLayout(mParams);
        } else {
            if (mParams.x != mOffset) {
                int x = mScreenSize.getWidth() - viewSize.getWidth() - mOffset;
                if (isLandscape) {
                    x -= Utils.getNavigationBarHeight();
                }
               moveToEdge(x, mParams.y);
            }
        }
    }


    interface Listener {
        /**
         * Called when cancel is clicked. This view is unusable once this
         * callback is invoked.
         */
        void onCancelClick();

        /**
         * Called when start is clicked and it is appropriate to start
         * recording. This view will hide itself completely before invoking this
         * callback.
         */
        void onStartClick();

        /**
         * Called when stop is clicked. This view is unusable once this callback
         * is invoked.
         */
        void onStopClick();

        void updateViewLayout(WindowManager.LayoutParams wmParams);
    }
}
